/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.auth.server.handler;

import cn.hutool.core.collection.CollUtil;
import com.iwindplus.auth.domain.enums.AuthModuleEnum;
import com.iwindplus.auth.domain.event.LoginLogEvent;
import com.iwindplus.auth.server.config.property.AuthProperty;
import com.iwindplus.boot.domain.constant.CommonConstant.ExceptionConstant;
import com.iwindplus.boot.domain.vo.ResultVO;
import com.iwindplus.boot.domain.vo.UserBaseVO;
import com.iwindplus.boot.util.JacksonUtil;
import com.iwindplus.boot.web.domain.property.GlobalErrorProperty;
import com.iwindplus.boot.web.domain.property.ResponseBodyProperty;
import com.iwindplus.boot.web.mvc.handler.GlobalErrorHandler;
import com.iwindplus.log.domain.dto.LoginLogDTO;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.Payload;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

/**
 * 认证成功处理器
 *
 * @author zengdegui
 * @since 2024/05/22 22:20
 */
@Slf4j
public record CustomAuthenticationSuccessHandler(AuthProperty property, GlobalErrorProperty globalErrorProperty,
                                                 ResponseBodyProperty responseBodyProperty,
                                                 ApplicationContext applicationContext) implements AuthenticationSuccessHandler {

    private static final Converter<OAuth2AccessTokenResponse, Map<String, Object>> ACCESS_TOKEN_RESPONSE_PARAMETERS_CONVERTER = new DefaultOAuth2AccessTokenResponseMapConverter();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;

        OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
        OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
        Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();

        OAuth2AccessTokenResponse.Builder builder =
            OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
                .tokenType(accessToken.getTokenType());
        if (Objects.nonNull(accessToken.getIssuedAt()) && Objects.nonNull(accessToken.getExpiresAt())) {
            builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
        }
        if (Objects.nonNull(refreshToken)) {
            builder.refreshToken(refreshToken.getTokenValue());
        }
        if (CollUtil.isNotEmpty(additionalParameters)) {
            builder.additionalParameters(additionalParameters);
        }
        OAuth2AccessTokenResponse accessTokenResponse = builder.build();
        Map<String, Object> tokenResponseParameters = ACCESS_TOKEN_RESPONSE_PARAMETERS_CONVERTER.convert(accessTokenResponse);

        // 返回响应信息
        GlobalErrorHandler.responseData(response, this.globalErrorProperty, this.responseBodyProperty, HttpStatus.OK,
            ResultVO.success(tokenResponseParameters));

        // 记录登录日志
        if (Boolean.TRUE.equals(property.getLog().getEnabled())) {
            LoginLogDTO entity;
            String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
            if (OAuth2ParameterNames.REFRESH_TOKEN.equals(grantType)) {
                entity = CustomAuthenticationSuccessHandler.buildLoginLog(accessToken, AuthModuleEnum.REFRESH_TOKEN.getValue(),
                    AuthModuleEnum.REFRESH_TOKEN.getDesc());
            } else {
                entity = CustomAuthenticationSuccessHandler.buildLoginLog(accessToken, AuthModuleEnum.LOGIN.getValue(),
                    AuthModuleEnum.LOGIN.getDesc());
            }
            this.applicationContext.publishEvent(new LoginLogEvent(this, entity));
        }
    }

    /**
     * LoginLogDTO.
     *
     * @param accessToken accessToken
     * @param moduleName  moduleName
     * @param moduleDesc  moduleDesc
     * @return LoginLogDO
     */
    public static LoginLogDTO buildLoginLog(OAuth2AccessToken accessToken, String moduleName, String moduleDesc) {
        UserBaseVO data = null;
        try {
            JWSObject jwsObject = JWSObject.parse(accessToken.getTokenValue());
            final Payload payload = jwsObject.getPayload();
            data = JacksonUtil.parseObject(payload.toString(), UserBaseVO.class);
        } catch (ParseException ex) {
            log.error(ExceptionConstant.EXCEPTION, ex);
        }
        if (Objects.isNull(data)) {
            return null;
        }
        return LoginLogDTO
            .builder()
            .moduleName(moduleName)
            .moduleDesc(moduleDesc)
            .userId(data.getUserId())
            .orgId(data.getOrgId())
            .createdBy(data.getRealName())
            .createdId(data.getUserId())
            .modifiedBy(data.getRealName())
            .modifiedId(data.getUserId())
            .build();
    }
}
